from re import split as resplit
from os import environ, system
from time import sleep
from datetime import datetime
from shutil import copyfileobj
from multiprocessing import Pool, Value, cpu_count
from ctypes import c_bool

from selenium.webdriver.common.by import By
from requests import get
from bs4 import BeautifulSoup
from uuid import uuid4

from .funcs import (checkPathResources, drive, notifySend, numType, loadImage,
                    countPercent, singleNotify)


counter = Value('i', 0)
checker = Value(c_bool, False)
dead = Value(c_bool, False)


class CheckAniMa():
    def __init__(self, led):
        super(CheckAniMa, self).__init__()
        self.dead = False
        self.led = led

    def __preRM(self, url):
        self.driver.get(url)
        parent = self.driver.find_element(By.ID, 'last-updates')

        fuzu_links = parent.find_elements(By.CLASS_NAME, 'non-hover')
        flinks = [i.get_attribute('href') for i in fuzu_links]
        link = [i for i in flinks if i is not None]

        subj = parent.find_elements(By.CLASS_NAME, 'tile')
        obj = [i.find_elements(By.CLASS_NAME, 'chapter-link') for i in subj]

        description = parent.find_elements(By.CLASS_NAME, 'desc')
        h3 = [i.find_element(By.TAG_NAME, 'h3') for i in description]
        a = [i.find_element(By.TAG_NAME, 'a') for i in h3]
        name = [i.get_attribute('title') for i in a]
        track = [i.get_attribute('href') for i in a]

        fch = [i[0].get_attribute('href').split('/')[-1].split('?')[0]
                        if len(i) > 0 else '0' for i in obj]
        ech = [i[-1].get_attribute('href').split('/')[-1].split('?')[0]
                        if len(i) > 0 else '0' for i in obj]
        ch = [fch[i] if numType(fch[i]) >= numType(ech[i]) else ech[i]
                                                    for i in range(len(obj))]
        note = [(v, ch[i]) for i, v in enumerate(link)
                           for j in self.fuzy if j in v]
        return note, (name, track) 

    def __preCheckMC(self, connect):
        soup = connect('https://manga-chan.me/newestch')
        parent = soup.find('ul', class_='area_rightNews linkStyle')
        li = parent.find_all('li')

        fuzy_dates = [i.find('div').text for i in li]
        dates = [i for i in fuzy_dates if i in 'Сегодня  ']
        fuzys = [i.find_all('a') for i in li]
        links = tuple(set([i[0]['href'] for i in fuzys][:len(dates)-1]))

        track = [i[0]['href'] for i in fuzys][:len(dates)-1]
        name = [i.text.split('Сегодня')[0] for i in li][:len(dates)-1]

        return [i for i in links for j in self.fuzy if j in i], (name, track)

    def __preCheckPoisk(self, url):
        raw, links, name, pages = [], [], [], 1
        while True:
            self.driver.get(f'{url}&page={pages}' if pages > 1 else url)
            page = self.driver.find_elements(By.CLASS_NAME, 'list-group-item')
            raw_names = self.driver.find_elements(By.CLASS_NAME, 'card-title')
            links += [i.find_element(By.TAG_NAME, 'a').get_attribute('href')
                                                                 for i in page]
            raw += [i.text for i in page]
            name += [i.get_attribute('innerText') for i in raw_names]
            if 'день назад' in " ".join(raw): break
            pages += 1
        li = [numType(i.split(' - ')[1].split()[0]) for i in raw
                                                            if 'ч. назад' in i]
        link = [i.split('/chapter')[0] for i in links]
        note = [(v, li[i]) for i, v in enumerate(link)
                           for j in self.fuzy if j in v]

        return note, (name[:len(li)], link[:len(li)])

    def __checkMC(self):
        connect = lambda url: BeautifulSoup(get(url).text, 'html.parser')
        notify = []
        check, track = self.__preCheckMC(connect)
        if check:
            for i in check:
                sp = connect(i)
                last = sp.find('div', class_="manga2").text
                number = numType(last.split('Глава ')[1].split()[0])
                notify.append((i, number))

        return notify, track

    def __checkRaMs(self, url):
        day = datetime.now().strftime('%d-%m-%Y')
        hot, two = self.__preRM(f'{url}/news/calendar/day/{day}')

        return hot, two

    def multiple(self, url):
        self.led.setToolTip('Tracking: ' + url.split('//')[1].split('.')[0])
        rezult, track = self.__checkMC() if 'manga-chan' in url else \
                        self.__preCheckPoisk(url) if 'mangapoisk' \
                            in url else self.__checkRaMs(url)
        return rezult, track

    def outputsMulitple(self):
        track, self.driver = [], drive()
        urls = ('https://readmanga.live/', 'https://mintmanga.live/',
                'https://manga-chan.me/newestch',
                'https://mangapoisk.ru/manga?sortBy=-last_chapter_at')
        try:
            for i in urls:
                try:
                    if self.dead: raise Exception('<<<Break tracking>>>')
                    track.append(self.multiple(i))
                except:
                    track.append(None)
                    print(None, i)
        finally:
            self.dead = False
            self.driver.close()
            self.driver.quit()

        return sum([i[0] if i is not None else [] for i in track], []), \
               [i[1][0] if i is not None else [] for i in track], \
               [i[1][1] if i is not None else [] for i in track]

    def run(self, data):
        self.fuzy = [resplit('[A-Z]', i, 2)[0] for i in data['manga']['urls']]
        urls, *track = self.outputsMulitple()
        msg, change = [], []
        fuzy_urls = [(self.fuzy.index(j), numType(i[1])) for j in self.fuzy
                                                 for i in urls if j in i[0]]
        for i in fuzy_urls:
            if data['manga']['numbers'][i[0]] > i[1]: continue
            n, _, data['notify']['manga'] = notifySend(data['notify']['manga'],
                                                data['manga']['numbers'][i[0]],
                                                i[1],
                                                data['manga']['names'][i[0]])
            msg.append(n) if n is not None else None
            change.append((i[0], i[1])) if n is not None else None

        data['manga']['track-name'] = track[0]
        data['manga']['track-link'] = track[1]

        if msg:
            data['notify']['manga'] += msg
            for i in change:
                data['manga']['change_numbers'][i[0]] = i[1]

        return data, len(msg)


class ParseManga():
    def __init__(self):
        super(ParseManga, self).__init__()
        self.percent, self.dead, self.start = 0, False, False
        self.path = f'{environ["HOME"]}/.cache/anima'
        self.default()

    def __mangaChan(self, driver):
        end = (driver.find_elements(By.CLASS_NAME,
                'item2')[4].text, 'продолжается')
        manga = driver.find_elements(By.CLASS_NAME,
            'manga2')[0].text.split(' Глава ')[1].split()[0]
        manga = numType(manga)
        name = driver.find_element(By.CLASS_NAME,
            'title_top_a').text.split('(')[-1][:-1:]
        img = driver.find_element(By.ID, 'cover').get_attribute('src')
        desc = driver.find_element(By.ID, 'description').text
        return end, manga, name, img, desc

    def __checkExtra(self, driver):
        try:
            manga_ = driver.find_element(By.CLASS_NAME, 'mt-3')
        except:
            manga_ = None
        try:
            alist = driver.find_elements(By.CLASS_NAME, 'item-title')
        except:
            alist = None

        manga = 'NUL'
        manga_c = manga_.text.split() if manga_ is not None else []

        if alist is None or not alist:
            manga = "None"
        elif 'Экстра' in (alist[0].text, *manga_c) or manga_ is None:
            counter = -1
            if len(alist) == 1:
                man = alist[0].get_attribute('innerText')
                item = man.split(' - ')[1].split()[0]
                manga = float(item) if '.' in item else int(item)
            else:
                for j in range(len(alist)):
                    element = alist[j].get_attribute('innerText')
                    child = element.split(' - ')
                    counter += 1
                    if len(child) > 1:
                        child = child[1].split()[0]
                        child = float(child) if '.' in child else \
                                'DIE' if 'Экстра' in element else \
                                int(child)
                    else: continue
                    if isinstance(child, (int, float)):
                        manga = float(f'{child}.{counter}') \
                            if isinstance(child, int) else \
                            round(child + float(f'.{counter}'), 2)
                        break
        ch = '0' if manga_ is None else \
             manga_c[-2 if 'новое' in manga_c[-1] else -1]
        return manga if not isinstance(manga, str) else numType(ch)

    def __readManga(self, driver):
        end = (driver.find_element(By.CLASS_NAME,
                'subject-meta').text, 'продолжается')
        manga = self.__checkExtra(driver)
        name = driver.find_element(By.CLASS_NAME, 'name').text
        img = driver.find_elements(By.CLASS_NAME,
                                    'fotorama__img')[0].get_attribute('src')
        desc = driver.find_element(By.CLASS_NAME, 'manga-description')

        return end, manga, name, img, desc.get_attribute('innerText')

    def __checkValueManga(self, driver):
        try:
            ch, dig = driver.find_elements(By.CLASS_NAME, 'chapter-title'), 0
        except:
            ch = None

        if ch is None or not ch:
            return 0
        else:
            while True:
                try:
                    pre = ch[0].text.split(' Глава ')[1].split()[0]
                    digit = float(pre) + float(f'.{dig}') if '.' in pre else \
                            numType(f'{pre}.{dig}' if dig else pre)
                    break
                except:
                    del(ch[0])
                    dig += 1
            return digit

    def __mangaPoisk(self, driver):
        name = driver.find_element(By.CLASS_NAME, 'post-name').text
        desc = driver.find_element(By.CLASS_NAME, 'manga-description').text
        img = driver.find_element(By.CLASS_NAME,
                                  'img-fluid').get_attribute('src')
        manga = self.__checkValueManga(driver)
        prefix = driver.find_element(By.CLASS_NAME,
                                     'post-info').get_attribute('innerText')
        end = 'продолжается' if 'Выпускается' in prefix else 'завершено'

        return (end, 'продолжается'), manga, name, img, desc

    def __checked(self, data, rezult, ind):
        end, manga, name = rezult
        data['manga']['ended'][ind] = 'stoped' \
            if 'приостановлен' in end[0] else 'ongoing' \
            if end[1] in end[0] else 'end'
        data['manga']['change_numbers'][ind] = manga
        note, lg, data['notify']['manga'] = notifySend(data['notify']['manga'],
                                                data['manga']['numbers'][ind],
                                                manga, name)
        log = f'{name} = {manga}\n' if lg is None else f'{lg}\n'

        return data, note, log

    def __updated(self, data, rezult, ind):
        name, img, desc = rezult
        pre = str(uuid4()) if len(name) > 127 else \
              '_'.join(name.split()).replace(',', '_')
        image = ('description/raw/', f"{pre}_{img.split('/')[-1]}")
        checkPathResources()
        r = get(img, stream=True)
        if r.status_code == 200:
            with open(f'{self.path}/{"".join(image)}', 'wb') as f:
                r.raw.decode_content = True
                copyfileobj(r.raw, f)
        img = loadImage(image[1]) or 'description/raw/error.png'
        data['manga']['images'][ind] = img
        data['manga']['description'][ind] = desc
        data['manga']['names'][ind] = name

        return data

    def default(self):
        global dead, counter, checker
        dead.value, counter.value, checker.value = False, 0, False

    def getFormat(self):
        global counter
        return f'%p% [{counter.value} - {self.length}]' \
                                                       if self.start else '%p%'

    def getPercent(self):
        global counter
        return countPercent(counter.value, self.length) if self.start else 0

    def breaking(self):
        global dead
        with dead.get_lock():
            dead.value = True

    def run(self, data, update):
        """ Check or update data of manga. """
        global checker
        self.default()
        data['manga']['logs'], self.data, ps = '', data, cpu_count()
        self.length, self.start = len(self.data['manga']['urls']), True
        check, checker.value = 0, True if update else False

        with Pool(processes=ps) as proc:
            start = proc.map(self.multiStart, data['manga']['urls'])

        self.start = False

        for i in start:
            if i is None: continue
            self.data['manga']['urls'][i[0]] = i[1]
            self.data['manga']['change_numbers'][i[0]] = i[2]
            self.data['manga']['ended'][i[0]] = i[3]
            self.data['manga']['logs'] += i[4]
            if update:
                self.data['manga']['names'][i[0]] = i[5]
                self.data['manga']['description'][i[0]] = i[6]
                self.data['manga']['images'][i[0]] = i[7]
            single = singleNotify(self.data['notify']['manga'],
                                  self.data['manga']['names'][i[0]]) \
                        if i[8] else data['notify']['manga']
            self.data['notify']['manga'] = single
            self.data['notify']['manga'].append(i[8]) if i[8] else None
            check += 1 if i[8] else 0

        return self.data, (check, 1)

    def __condition(self, index):
        global checker
        return True if self.data['manga']['names'][index] and \
            'description/small' in self.data['manga']['images'][index] and \
            self.data['manga']['description'][index] and checker.value or \
            self.data['manga']['ended'][index] == 'end' else False

    def __driverProcess(self, url, index):
        global checker
        try:
            driver = drive()
            driver.get(url)
            self.data['manga']['urls'][index] = driver.current_url
            if 'https://manga-chan.me' in url:
                rezult = self.__mangaChan(driver)
            elif 'https://mangapoisk.ru' in url:
                rezult = self.__mangaPoisk(driver)
            else:
                rezult = self.__readManga(driver)
            self.data, msg, log = self.__checked(self.data, rezult[:3], index)
            if checker.value:
                self.data = self.__updated(self.data, rezult[2:], index)
            return self.data, msg, log
        except Exception as e:
            system(f'notify-send "Error check manga [{index}]\n{e}"')
        finally:
            driver.close()
            driver.quit()
        return self.data, '', ''

    def multiStart(self, url):
        global counter, dead
        if not dead.value:
            index = self.data['manga']['urls'].index(url)
            with counter.get_lock():
                counter.value += 1
            if self.__condition(index):
                sleep(.07 if self.length // 60 > 0 else .4)
            else:
                self.data, msg, log = self.__driverProcess(url, index)
                if self.data and msg and log:
                    return index, self.data['manga']['urls'][index], \
                           self.data['manga']['change_numbers'][index], \
                           self.data['manga']['ended'][index], log, \
                           self.data['manga']['names'][index], \
                           self.data['manga']['description'][index], \
                           self.data['manga']['images'][index], msg
        return None
